
#include "../iface/debug.h"
#include "../iface/testerror.h"
#include <apr_strings.h>
#include "Resolver.h"

/** A set of reserved names which are denied to the renamer functions. */
const char *Resolver::reserved_names[] = {
   "type", "progress", "progress_cached", "result", "run", NULL };

/** The set of all metadata fields, their full names and cacheability state. */
const ResolveMetadataMetadata Resolver::metadata_names[] = {
   { "id",                "Actor-name",                     Resolver::ITEM_CACHEABLE_ALWAYS },
   { "class",             "Actor-class",                    Resolver::ITEM_CACHEABLE_ALWAYS },
   { "parent",            "Actor-parent",                   Resolver::ITEM_CACHEABLE_ALWAYS },
   { "datecreated",       "Actor-createdon",                Resolver::ITEM_CACHEABLE_ALWAYS },
   { "datevalid",         "Actor-validfrom",                Resolver::ITEM_CACHEABLE_IF_NONEMPTY },
   { "dateexpires",       "Actor-expireson",                Resolver::ITEM_CACHEABLE_IF_NONEMPTY },
   { "lastaccess",        "Actor-lastaccess",               Resolver::ITEM_NOT_CACHEABLE },
   { "marks",             "Actor-actormark",                Resolver::ITEM_NOT_CACHEABLE },
   { "localname",         "Actor-externalalias",            Resolver::ITEM_CACHEABLE_IF_NONEMPTY },
   { "doneblocks",        "Actor-blocksdone",               Resolver::ITEM_NOT_CACHEABLE },
   { "totalblocks",       "Actor-blockstotal",              Resolver::ITEM_NOT_CACHEABLE },
   { "credit",            "User-credit",                    Resolver::ITEM_NOT_CACHEABLE },
   { "quota",             "User-quota",                     Resolver::ITEM_NOT_CACHEABLE },
   { "host",              "Compute-host",                   Resolver::ITEM_NOT_CACHEABLE },
   { "capabilities",      "Compute-capabilities",           Resolver::ITEM_NOT_CACHEABLE },
   { "localid",           "WorkInstance-instanceid",        Resolver::ITEM_CACHEABLE_IF_NONEMPTY },
   { "cluster",           "WorkInstance-assignedcluster",   Resolver::ITEM_NOT_CACHEABLE },
   { "preferredclusters", "WorkInstance-preferredclusters", Resolver::ITEM_CACHEABLE_ALWAYS },
   { "inputdatastore",    "WorkInstance-inputdatastore",    Resolver::ITEM_CACHEABLE_ALWAYS },
   { "outputcompute",     "WorkInstance-outputcompute",     Resolver::ITEM_NOT_CACHEABLE },
   { "resultblocks",      "WorkInstance-resultblocks",      Resolver::ITEM_NOT_CACHEABLE },
   { "datablockcount",    "DataStore-blocksheld",           Resolver::ITEM_NOT_CACHEABLE },
   { "datablocksize",     "DataStore-blocksize",            Resolver::ITEM_CACHEABLE_ALWAYS },
   { "datahash",          "DataStore-datahash",             Resolver::ITEM_CACHEABLE_ALWAYS },
   { "workinstances",     "DataStore-instances",            Resolver::ITEM_CACHEABLE_ALWAYS },
   { "originatedfrom",    "DataStore-origin",               Resolver::ITEM_CACHEABLE_IF_NONEMPTY },
   { "capabilities",      "DataStore-capabilities",         Resolver::ITEM_CACHEABLE_IF_NONEMPTY },
   { "decryptablewith",   "DataStore-decryptionkey",        Resolver::ITEM_CACHEABLE_ALWAYS },
   { NULL,                NULL,                             Resolver::ITEM_NOT_CACHEABLE } };

/** Construct the abstract resolver.
 * 
 * @param imp
 * The memory pool to use to create the memory pool for this class. */
Resolver::Resolver(apr_pool_t *imp)
{
   initPool(imp);
   valid = 0;
}

/** Destructor for the abstract resolver. */
Resolver::~Resolver()
{
   clearCache();
   apr_pool_destroy(mp);
   apr_pool_destroy(pmp);
}

/** Reset the resolver (destroy the temporary memory pool and then
 * reinstate it). */
void Resolver::reset()
{
   apr_pool_clear(mp);
   apr_pool_destroy(mp);
   apr_status_t status;
   status = apr_pool_create(&mp, parent_pool);
}

/** Clear the cache of all resolved items. */
void Resolver::clearCache()
{
   caching_map.clear();
}

/** Get an item from the cache.
 * 
 * @param hierarchy The hierarchy name for the item.
 * 
 * @return A constant string of the data in this item,
 * or NULL if it is not cached at this location. */
const char *Resolver::getCached(const char *hierarchy)
{
   FATAL_IF_BLANK(NULL, hierarchy, "hierarchy lookup");
   std::map<std::string, std::string>::iterator found_item = caching_map.find(std::string(hierarchy));
   if (found_item != caching_map.end())
      return found_item->second.c_str();
   return NULL;
}

/** Get an item from the cache that was marked as
 * temporary (expirable) data.
 * 
 * @param hierarchy The hierarchy name for the item.
 * 
 * @return A constant string of the data in this item,
 * or NULL if it is not cached at this expirable location. */
const char *Resolver::getTempCached(const char *hierarchy)
{
   FATAL_IF_BLANK(NULL, hierarchy, "hierarchy lookup");
   std::string nstring(std::string(hierarchy) + std::string("_cached"));
   const char *ret_string = getCached(nstring.c_str());
   return ret_string;
}

/** Initialise the memory pool.
 * 
 * @param iparent
 * The parent memory pool to initialise the permanent and
 * temporary memory pool for this class. */
void Resolver::initPool(apr_pool_t *iparent)
{
   apr_status_t status;
   parent_pool = iparent;
   status = apr_pool_create(&mp, iparent);
   status = apr_pool_create(&pmp, iparent);
}

/** Validate the new alias name against all the possible predefined
 * properties of the actors.
 * 
 * @param newname
 * The new name to be validated.
 * 
 * @return Zero where the name is okay, and less than zero indicating
 * the problem when the name clashes with a property or other predefined
 * behaviour. */
int Resolver::validateNewNameNotIllegal(const char *newname)
{
   // A new name may not have any dots, as these would confuse the resolver.
   if (strstr(newname, ".") != NULL)
   {
      errprint("new alias name provided '%s' has a dot", newname);
      return -2;
   }
   // Also cannot match a reserved name
   int count = 0;
   while (reserved_names[count] != NULL)
   {
      if (strcmp(newname, reserved_names[count]) == 0)
      {
         errprint("'%s' is a reserved name", newname);
         return -3;
      }
      count++;
   }
   count = 0;
   while (metadata_names[count].simple_key != NULL)
   {
      if (strcmp(newname, metadata_names[count].simple_key) == 0)
      {
         errprint("'%s' is a reserved name", newname);
         return -3;
      }
      count++;
   }
   // Cannot end in _cached
   if (strstr(newname, "_cached") != NULL)
   {
      errprint("'%s' cannot contain '_cached'\n", newname);
      return -4;
   }
   // All the other tests require some form of database access.
   return 0;
}

/** Get an item from the cache if it exists, or from the
 * store if it is not cached.
 * 
 * @param hierarchy The hierarchy name for the item.
 * 
 * @return A constant string of the data in this item,
 * or NULL if it is not cached at this location. */
const char *Resolver::get(const char *hierarchy)
{
   if (hierarchy == NULL) return NULL;
   const char *cached_result = getCached(hierarchy);
   if (cached_result) return cached_result;
   return refresh(hierarchy);
}

/** Get an item from the store, regardless of whether
 * it is cached or not.
 * 
 * @param hierarchy The hierarchy name for the item.
 * 
 * @return A constant string of the data in this item,
 * or NULL if it is not cached at this location. */
const char *Resolver::refresh(const char *hierarchy)
{
   if (hierarchy == NULL) return NULL;
   int last_dot_idx = 0;
   for (int i = 0; i < strlen(hierarchy); i++)
      if (hierarchy[i] == '.')
         last_dot_idx = i;
   const char *endstr = &(hierarchy[last_dot_idx + 1]);
   if (last_dot_idx == 0)
      endstr = "";
   const char *parent_entity = apr_pstrdup(mp, hierarchy);
   const char *retstr = NULL;
   (const_cast<char *>(parent_entity))[last_dot_idx] = '\0';
   if (strcmp(endstr, "type") == 0)
   {
      retstr = resolveType(parent_entity);
   } else if (strcmp(endstr, "progress") == 0)
   {
      retstr = resolveProgress(parent_entity);
   } else if ((strcmp(endstr, "parent") == 0) || (strcmp(endstr, "parentname") == 0))
   {
      retstr = resolveParent(parent_entity, endstr);
   } else if ((strcmp(endstr, "children") == 0) || (strcmp(endstr, "childrennames") == 0))
   {
      retstr = resolveChildren(parent_entity, endstr);
   } else
   {
      int flag_metadata = 0;
      int count = 0;
      while (metadata_names[count].simple_key)
      {
         if (strcmp(metadata_names[count].simple_key, endstr) == 0)
            flag_metadata = 1;
         count++;
      }
      if (flag_metadata)
      {
         retstr = resolveMetadata(parent_entity, endstr);
      } else
      {
         retstr = resolveName(hierarchy);
      }
   }
   reset();
   return retstr;
}

/** Cache the name with the given result, and return the cached result
 * as a more permanent string.
 * 
 * @param hierarchy The hierarchy name for the item.
 * 
 * @param resolution The result to be associated with the item.
 * 
 * @return The more permanent cached copy of the given result. */
const char *Resolver::cacheAndReturn(const char *hierarchy, const char *resolution)
{
   FATAL_IF_BLANK(NULL, resolution, "resolved name");
   FATAL_IF_BLANK(NULL, hierarchy, "hierarchy lookup");
   caching_map[std::string(hierarchy)] = std::string(resolution);
   return getCached(hierarchy);
}

/** Cache the name with the given temporary result, and return
 * the cached result as a more permanent string.
 * 
 * @param hierarchy The hierarchy name for the item.
 * 
 * @param resolution The result to be associated temporarily with the item.
 * 
 * @return The more permanent cached copy of the given temporary result. */
const char *Resolver::cacheTempAndReturn(const char *hierarchy, const char *resolution)
{
   FATAL_IF_BLANK(NULL, resolution, "resolved name");
   FATAL_IF_BLANK(NULL, hierarchy, "hierarchy lookup");
   caching_map[std::string(hierarchy) + std::string("_cached")] = std::string(resolution);
   std::string nstring(std::string(hierarchy) + std::string("_cached"));
   const char *ret_string = getCached(nstring.c_str());
   return ret_string;
}
